/**
 * File: TraverseGraphIntoBuckets.java
 * Created by: mhaimel
 * Created on: Apr 30, 2010
 * CVS:  $Id: TraverseGraphIntoBuckets.java 1.0 Apr 30, 2010 9:58:15 AM mhaimel Exp $
 */
package uk.ac.ebi.curtain.controller.graph;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import uk.ac.ebi.curtain.controller.BucketNodeInfoBean;
import uk.ac.ebi.curtain.controller.SingleSubmitController;
import uk.ac.ebi.curtain.model.graph.Arc;
import uk.ac.ebi.curtain.model.graph.Graph;
import uk.ac.ebi.curtain.model.graph.Node;
import uk.ac.ebi.curtain.model.graph.NodeEdge;
import uk.ac.ebi.curtain.model.graph.GraphAccess.TouchEach;
import uk.ac.ebi.curtain.model.graph.curtain.ContigInfo;
import uk.ac.ebi.curtain.model.graph.curtain.CurtainGraph;
import uk.ac.ebi.curtain.model.graph.curtain.ReadWrapper;
import uk.ac.ebi.curtain.model.graph.curtain.TraverseHelper;
import uk.ac.ebi.curtain.model.graph.impl.NodeStatusReset;
import uk.ac.ebi.curtain.utils.StatisticHelper;

/**
 * @author mhaimel
 *
 */
public class TraverseGraphToBucketsRepeatAware extends SingleSubmitController<Serializable, Serializable> {
//	private static final int USED_FLAG = (int)Math.pow(2, 0);
	
	private static class SeedComparator implements Comparator<Node<ContigInfo,ReadWrapper>>{
		@Override
		public int compare(Node<ContigInfo, ReadWrapper> o1,Node<ContigInfo, ReadWrapper> o2) {
			ContigInfo val1 = o1.getValue();
			ContigInfo val2 = o2.getValue();
			int ret = val2.getLength().compareTo(val1.getLength()); // decreasing
			int cat = val1.getCategories();
			if(ret == 0){
				Long cov1 = 0l;
				Long cov2 = 0l;
				for(int i = 0; i<cat; ++i ){
					cov1 += val1.getCoverageBpCount(i);
					cov2 += val2.getCoverageBpCount(i);
				}
				ret = cov2.compareTo(cov1);
			}
			if(ret == 0){
				ret = o1.getId().compareTo(o2.getId());
			}
			return ret;
		}
	}

	@Override
	protected void singleSubmit(List<Serializable> inputList) {
		CurtainGraph cGraph = getContext().getGraph();
		Graph<ContigInfo, ReadWrapper> graph = cGraph.getGraph();
		// TODO Check Contig flag for partial edge removed!!!
		/* Rest status for NodeNextSeed */
		graph.processNodes(new NodeStatusReset<ContigInfo, ReadWrapper>(0));
		
		final ConcurrentSkipListSet<Node<ContigInfo, ReadWrapper>> nodeSorted = new ConcurrentSkipListSet<Node<ContigInfo,ReadWrapper>>(
				new SeedComparator());
		graph.processNodes(new NodeStatusReset(0));
		
		graph.processNodes(new TouchEach<Node<ContigInfo, ReadWrapper>>() {

			@Override
			public void touch(Node<ContigInfo, ReadWrapper> element) {
				nodeSorted.add(element);
			}
		});
		AtomicInteger bucketIdCnt = new AtomicInteger(0);
		AtomicInteger totalCnt = new AtomicInteger(0);
		AtomicLong totalBp = new AtomicLong(0);
		AtomicLong totalLen = new AtomicLong(0);
		Node<ContigInfo, ReadWrapper> seed = nodeSorted.pollFirst();
		int minLength = getConfig().getUniqueMinContigLength();
		while(null != seed){
			if(!seed.isActive()){
				// not active
				if(getLog().isTraceEnabled()){
					getLog().trace("Seed not active: " + seed.getId());
				}
			} else if(TraverseHelper.gotUsed(seed)){
				if(getLog().isTraceEnabled()){
					getLog().trace("Seed already used: " + seed.getId());
				}
			} else if(seed.getValue().getLength() < minLength && seed.getArcs(NodeEdge.Start).isEmpty() && seed.getArcs(NodeEdge.End).isEmpty()){
				if(getLog().isTraceEnabled()){
					getLog().trace("Seed short and not connected: " + seed.getId());
				}
			} else {
				Collection<Set<Node<ContigInfo, ReadWrapper>>> clusterSet = clusterContigs(seed);
				totalBp.set(0l);
				totalLen.set(0l);
				for(Set<Node<ContigInfo, ReadWrapper>> nodeCluster : clusterSet){
					for(Node<ContigInfo, ReadWrapper> n : nodeCluster){
						totalLen.addAndGet(n.getValue().getLength());
						for(int i = 0; i < n.getValue().getCategories(); ++i){
							totalBp.addAndGet(n.getValue().getCoverageBpCount(i));
						}
					}
					if(!nodeCluster.isEmpty()){
						totalCnt.addAndGet(nodeCluster.size());
						getLog().debug("Total bucket contig size of: \t" + bucketIdCnt.get() + "\t"+nodeCluster.size()+"\t" + totalLen + "\t"+totalBp);
						submitCluster(bucketIdCnt.getAndIncrement(),nodeCluster);
					} else {
						getLog().debug("Seed Node " + seed + " did not cluster!");
					}
				}
			}
			seed = nodeSorted.pollFirst();
		}
		getLog().info("Seeds used: " + bucketIdCnt + "; Nodes used: " + totalCnt + "; Total nodes: " + graph.getNodeSize()+ ";");
		final ArrayList<Integer> len = new ArrayList<Integer>();
		final AtomicInteger nWithConn = new AtomicInteger(0);
		graph.processNodes(new TouchEach<Node<ContigInfo, ReadWrapper>>(){

			@Override
			public void touch(Node<ContigInfo, ReadWrapper> node) {
				if(null != node){
					if(node.isActive()){
						if(!TraverseHelper.gotUsed(node)){
							node.getStatus().get();
							len.add(node.getValue().getLength().intValue());
							if(!node.getArcs(NodeEdge.Start).isEmpty() || !node.getArcs(NodeEdge.End).isEmpty()){
								nWithConn.incrementAndGet();
							}
						}
					} else {
						node.getActive();
					}
				}				
			}});
		Collections.sort(len);
		System.out.println(StatisticHelper.getStatistic(len));
		getLog().info("Node with connections: " + nWithConn);		
	}

	private void submitCluster(int bucketId, Collection<Node<ContigInfo, ReadWrapper>> nodeCluster) {
		HashSet<Long> nodeIds = new HashSet<Long>(nodeCluster.size());
		ArrayList<Long> ctgIds = new ArrayList<Long>(nodeCluster.size());
		for(Node<ContigInfo, ReadWrapper> node : nodeCluster){
			nodeIds.add(node.getId());
			ctgIds.add(node.getValue().getId());
		}
		HashSet<Long> arcIds = new HashSet<Long>();
		for(Node<ContigInfo, ReadWrapper> node : nodeCluster){
			for(NodeEdge ne : NodeEdge.values()){
				for(Arc<ContigInfo, ReadWrapper> b: node.getArcs(ne)){
					Node<ContigInfo, ReadWrapper> bNode = b.getOtherNode(node);
					if(nodeIds.contains(bNode.getId())){
						arcIds.add(b.getValue().getId());
					}
				}
			}
		}
		BucketNodeInfoBean bean = new BucketNodeInfoBean(bucketId, nodeIds.toArray(new Long[0]), ctgIds.toArray(new Long[0]), arcIds.toArray(new Long[0]));
		submit(bean);
	}

	private Collection<Set<Node<ContigInfo, ReadWrapper>>> clusterContigs(Node<ContigInfo, ReadWrapper> seed) {
		Collection<Set<Node<ContigInfo, ReadWrapper>>> clusters = new LinkedList<Set<Node<ContigInfo,ReadWrapper>>>();
		Integer minLen = getConfig().getUniqueMinContigLength();
		long maxSize = getConfig().getMaxBucketSequenceLength();
		Integer cat = seed.getValue().getCategories();
		TraverseHelper helper = new TraverseHelper(cat, minLen, maxSize);
		helper.setRepeatAware(true);
		helper.registerSeed(seed);
		
		search: while(true){
//				!(queue.isEmpty() && currLib >= cat)){ // Take from start - add at the end
			if(helper.hasReachedSequenceLimit()){
//				clusters.add(helper.getNodes());
//				helper.newCluster();
				getLog().info("Bucket size exceeded: " + helper.getSequenceLength());
				break search; // Finish search
			}
			
			boolean force = false;
			while(helper.hasEmptyQueue()){
				/* iterate 2x through libraries
				  - 1st without force
				  - 2nd with FORCE over edge
				*/  
				if(!helper.hasMoreCategories()){
					if(force){						
						break search;
					}
					helper.resetLibrary();
					force = true;
				} else {
					helper.nextLibrary();
				}
				helper.fillQueue(1,force);
			}
			
			helper.resetLibrary();
			helper.processQueue();
			
		}
		Set<Node<ContigInfo, ReadWrapper>> set = helper.getNodes();
		if(!set.isEmpty()){
			clusters.add(set);
		}
		helper.finish();
		return clusters;
	}
	
}
